//
//    FILE: I2C_eeprom.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 1.2.03
// PURPOSE: I2C_eeprom library for Arduino with EEPROM 24LC256 et al.
//
// HISTORY:
// 0.1.00 - 2011-01-21 initial version
// 0.1.01 - 2011-02-07 added setBlock function
// 0.2.00 - 2011-02-11 fixed 64 bit boundary bug
// 0.2.01 - 2011-08-13 _readBlock made more robust + return value
// 1.0.00 - 2013-06-09 support for Arduino 1.0.x
// 1.0.01 - 2013-11-01 fixed writeBlock bug, refactor
// 1.0.02 - 2013-11-03 optimize internal buffers, refactor
// 1.0.03 - 2013-11-03 refactor 5 millis() write-latency
// 1.0.04 - 2013-11-03 fix bug in readBlock, moved waitEEReady()
//                     -> more efficient.
// 1.0.05 - 2013-11-06 improved waitEEReady(),
//                     added determineSize()
// 1.1.00 - 2013-11-13 added begin() function (Note breaking interface)
//                     use faster block Wire.write()
//                     int casting removed
// 1.2.00 - 2014-05-21 Added support for Arduino DUE ( thanks to Tyler F.)
// 1.2.01 - 2014-05-21 Refactoring
// 1.2.02 - 2015-03-06 stricter interface
// 1.2.03 - 2015-05-15 bugfix in _pageBlock & example (thanks ifreislich )
//
// Released to the public domain
//

#include <I2C_eeprom.h>

#if defined(ARDUINO) && ARDUINO >= 100
    #define WIRE_WRITE Wire.write
    #define WIRE_READ  Wire.read
#else
    #define WIRE_WRITE Wire.send
    #define WIRE_READ  Wire.receive
#endif


I2C_eeprom::I2C_eeprom(const uint8_t deviceAddress)
{
    I2C_eeprom(deviceAddress, I2C_EEPROM_PAGESIZE);
}

I2C_eeprom::I2C_eeprom(const uint8_t deviceAddress, const unsigned int deviceSize)
{
    _deviceAddress = deviceAddress;

    // Chips 16Kbit (2048 Bytes) or smaller only have one-word addresses.
    // Also try to guess page size from device size (going by Microchip 24LCXX datasheets here).
    if (deviceSize <= 256)
    {
        this->_isAddressSizeTwoWords = false;
        this->_pageSize = 8;
    }
    else if (deviceSize <= 256 * 8)
    {
        this->_isAddressSizeTwoWords = false;
        this->_pageSize = 16;
    }
    else
    {
        this->_isAddressSizeTwoWords = true;
        this->_pageSize = 32;
    }
}

void I2C_eeprom::begin()
{
    Wire.begin();
    _lastWrite = 0;

// TWBR is not available on Arduino Due
#ifdef TWBR
    TWBR = 72;
    // 0=1000 1=888 2=800 8=500
    // 12=400KHz  24=250 32=200  72=100  152=50
    // F_CPU/16+(2*TWBR) // TWBR is a uint8_t
#endif
}



int I2C_eeprom::writeByte(const uint16_t memoryAddress, const uint8_t data)
{
    int rv = _WriteBlock(memoryAddress, &data, 1);
    return rv;
}

int I2C_eeprom::setBlock(const uint16_t memoryAddress, const uint8_t data, const uint16_t length)
{
    uint8_t buffer[I2C_TWIBUFFERSIZE];
    for (uint8_t i = 0; i < I2C_TWIBUFFERSIZE; i++) buffer[i] = data;

    int rv = _pageBlock(memoryAddress, buffer, length, false);
    return rv;
}

int I2C_eeprom::writeBlock(const uint16_t memoryAddress, const uint8_t* buffer, const uint16_t length)
{
    int rv = _pageBlock(memoryAddress, buffer, length, true);
    return rv;
}

uint8_t I2C_eeprom::readByte(const uint16_t memoryAddress)
{
    uint8_t rdata;
    _ReadBlock(memoryAddress, &rdata, 1);
    return rdata;
}

uint16_t I2C_eeprom::readBlock(const uint16_t memoryAddress, uint8_t* buffer, const uint16_t length)
{
    uint16_t addr = memoryAddress;
    uint16_t len = length;
    uint16_t rv = 0;
    while (len > 0)
    {
        uint8_t cnt = min(len, I2C_TWIBUFFERSIZE);
        rv += _ReadBlock(addr, buffer, cnt);
        addr += cnt;
        buffer += cnt;
        len -= cnt;
    }
    return rv;
}

#ifdef I2C_EEPROM_EXTENDED
// returns 64, 32, 16, 8, 4, 2, 1, 0
// 0 is smaller than 1K
int I2C_eeprom::determineSize()
{
    int rv = 0;  // unknown
    uint8_t orgValues[8];
    uint16_t addr;

    // try to read a byte to see if connected
    rv += _ReadBlock(0x00, orgValues, 1);
    if (rv == 0) return -1;

    // remember old values, non destructive
    for (uint8_t i=0; i<8; i++)
    {
        addr = (512 << i) + 1;
        orgValues[i] = readByte(addr);
    }

    // scan page folding
    for (uint8_t i=0; i<8; i++)
    {
        rv = i;
        uint16_t addr1 = (512 << i) + 1;
        uint16_t addr2 = (512 << (i+1)) + 1;
        writeByte(addr1, 0xAA);
        writeByte(addr2, 0x55);
        if (readByte(addr1) == 0x55) // folded!
        {
            break;
        }
    }

    // restore original values
    for (uint8_t i=0; i<8; i++)
    {
        uint16_t addr = (512 << i) + 1;
        writeByte(addr, orgValues[i]);
    }
    return 0x01 << (rv-1);
}
#endif

////////////////////////////////////////////////////////////////////
//
// PRIVATE
//

// _pageBlock aligns buffer to page boundaries for writing.
// and to TWI buffer size
// returns 0 = OK otherwise error
int I2C_eeprom::_pageBlock(const uint16_t memoryAddress, const uint8_t* buffer, const uint16_t length, const bool incrBuffer)
{
    uint16_t addr = memoryAddress;
    uint16_t len = length;
    int rv = 0;
    while (len > 0)
    {
        uint8_t bytesUntilPageBoundary = this->_pageSize - addr % this->_pageSize;
        uint8_t cnt = min(len, bytesUntilPageBoundary);
        cnt = min(cnt, I2C_TWIBUFFERSIZE);

        int rv = _WriteBlock(addr, buffer, cnt);
        if (rv != 0) return rv;

        addr += cnt;
        if (incrBuffer) buffer += cnt;
        len -= cnt;
    }
    return rv;
}

// supports one and 2 bytes addresses
void I2C_eeprom::_beginTransmission(const uint16_t memoryAddress)
{
  Wire.beginTransmission(_deviceAddress);

  if (this->_isAddressSizeTwoWords)
  {
    WIRE_WRITE((memoryAddress >> 8));    // Address High Byte
  }

  WIRE_WRITE((memoryAddress & 0xFF));  // Address Low Byte (or only byte for chips 16K or smaller that only have one-word addresses)
}

// pre: length <= this->_pageSize  && length <= I2C_TWIBUFFERSIZE;
// returns 0 = OK otherwise error
int I2C_eeprom::_WriteBlock(const uint16_t memoryAddress, const uint8_t* buffer, const uint8_t length)
{
    waitEEReady();

    this->_beginTransmission(memoryAddress);

    WIRE_WRITE(buffer, length);

    int rv = Wire.endTransmission();
    _lastWrite = micros();
    return rv;
}

// pre: buffer is large enough to hold length bytes
// returns bytes read
uint8_t I2C_eeprom::_ReadBlock(const uint16_t memoryAddress, uint8_t* buffer, const uint8_t length)
{
    waitEEReady();

    this->_beginTransmission(memoryAddress);

    int rv = Wire.endTransmission();
    if (rv != 0) return 0;  // error

    Wire.requestFrom(_deviceAddress, length);
    uint8_t cnt = 0;
    uint32_t before = millis();
    while ((cnt < length) && ((millis() - before) < I2C_EEPROM_TIMEOUT))
    {
        if (Wire.available()) buffer[cnt++] = WIRE_READ();
    }
    return cnt;
}

void I2C_eeprom::waitEEReady()
{
#define I2C_WRITEDELAY  5000

    // Wait until EEPROM gives ACK again.
    // this is a bit faster than the hardcoded 5 milliSeconds
    while ((micros() - _lastWrite) <= I2C_WRITEDELAY)
    {
        Wire.beginTransmission(_deviceAddress);
        int x = Wire.endTransmission();
        if (x == 0) break;
    }
}

// END OF FILE
